/******************************************************************************
 * Key Ghost
 * by Erik Rainey
 *
 * 5/30/2004
 *
 * A simple key injector into Win32 programs. I used it to streamline tedious
 * typing of complex key sequences for my own testing.
 *
 * This code is covered under the GNU Public License, see attached license.
 *
 * The parsing language is rather simple. Essentially you create a text file
 * that dictates what, when and where each key stroke will go. The language
 * was designed with the idea that all keyboard characters are reachable.
 *
 * Here's an example

#Notepad
Untitled 1 - Notepad
!200
{_a+a}{_c+c}

 * Line 1: The Windows ClassName of the Application (if unsure, run keyghost
 * with -w to list all windows and their childern's windows. (It's
 * a long list). The # character is the indicator to the parser for this
 * attribute.
 * Line 2: The Title of the Window. This must match exactly. There is no
 * special parsing character for this.
 * Line 3: The '!' indicates a time delay between keys. All keys after this
 * will get this time delay. Use another ! to change it again. This is in ms.
 * Line 4:
 *      Character Translations:
 *          < and > ALT Down and ALT Up, respectively,
 *          { and } Control Down and Control Up, respectively
 *          ( and ) Shift Down and Shift Up, respectively
 *          _       key down. the next alphanumberic will be marked as a down key
 *          +       key up. the next alphanumberic will be marked as a up key
 *          a-z     The alphabet, case insensitive
 *          0-9     The numbers
 *          &       Tab             use (_7+7) to get a real &
 *          @       Return          use (_2+2) to get a real @
 *          *       F4              use (_8+8) to get a real *
 *          $       F5              use (_4+4) to get a real $
 *          ^       Up Key          use (_6+6) to get a real ^
 *          |       Down Key        you hopefully see the pattern now...
 *
 * So Line 3 literally decodes to
 * ALT DOWN, A DOWN, A UP, ALT UP, ALT DOWN, C DOWN, C UP, ALT UP
 * This would send a CTRL+A, then an CTRL+C to the program, thus copying all
 * the data in the window.
 *
{_d+d}_f+f decodes to CTRL+D then F
<_*+*>     decodes to ALT+F4
 *****************************************************************************/

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "windows.h"
#include "winuser.h"

typedef struct {
    int focus;
    int delay;
    BYTE scanCode;
    int direction;
} key_stroke;

//#define KEY_UP    (KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP)
//#define KEY_DW    (KEYEVENTF_EXTENDEDKEY | 0)

#define KEY_UP  (KEYEVENTF_KEYUP)
#define KEY_DW  (0)

typedef enum {
    ERROR_NONE,
    ERROR_PARSE,
    ERROR_ARGUMENT,
    ERROR_RANGE
} error_code;

typedef enum {
    KEY_STATE_NONE,
    KEY_STATE_DIRECTION,
    KEY_STATE_KEY
} key_state;

#define MAX_NUM_KEY_STROKES (50)
#define MAX_FOCUS_TARGETS   (10)
#define MAX_NAME_LENGTH     (100)
#define numElem(x) (sizeof(x) / sizeof(x[0]))
int key_index;
key_stroke keys[MAX_NUM_KEY_STROKES];
char classname[MAX_FOCUS_TARGETS][MAX_NAME_LENGTH];
char title[MAX_FOCUS_TARGETS][MAX_NAME_LENGTH];

#define IF_DIRECTION(vk)    {                   \
    if (last_state == KEY_STATE_DIRECTION)      \
    {                                           \
        keys[key_index].scanCode = vk;          \
        last_state = KEY_STATE_KEY;             \
        keys[key_index].focus = focusIndex;     \
        keys[key_index].delay = currentDelay;   \
        key_index++;                            \
    }                                           \
    else                                        \
    {                                           \
        printf("Direction ERROR!\n");           \
    }                                           \
}

#define MODIFIER(vk,dir)    {                   \
    if (last_state == KEY_STATE_DIRECTION)      \
    {                                           \
        err = ERROR_PARSE;                      \
        break;                                  \
    }                                           \
    last_state = KEY_STATE_KEY;                 \
    keys[key_index].direction = dir;            \
    keys[key_index].scanCode = vk;              \
    keys[key_index].focus = focusIndex;         \
    keys[key_index].delay = currentDelay;       \
    key_index++;                                \
}

/**
 * The usage statement.
 * @param argc The number of arguments
 * @param argv The array of arguments as strings.
 */
void Usage(int argc, char *argv[])
{
    printf("KeyGhost by Erik Rainey.\n");
    printf("This software is covered under the GPL 2.0\n");
    printf("==========================================\n");
    printf("$ %s -f <file.keys>   - This will run the keys file.\n", argv[0]);
    printf("$ %s -w               - This will enumerate all the current windows\n",argv[0]);
}


/**
 * The main parsing loop.
 * @param fp The FILE pointer to the key file.
 * @param keys The pointer to the keys structure.
 * @return Returns ERROR_NONE on success, anything else is a failure.
 */
error_code parseKeyStrokes(FILE *fp, key_stroke *keys)
{
    error_code err = ERROR_NONE;
    int focusIndex = -1;
    int currentDelay = 0;
    char c;
    int line = 0;
    key_state last_state = KEY_STATE_KEY;

    // clear out the keys
    memset((void *)keys,0,sizeof(keys));
    key_index = 0;

    // parse each line
    do
    {
        c = fgetc(fp);
        if (c == EOF)
            break;

        //printf("Parsing '%c' [0x%02x]\n",c,(c & 0xFF));
        //printf("last_state = %u\n",last_state);

        switch(c)
        {
        case '\n':
            line++;
            break;
        case '!':
        {
            // we're changing the time delay!
            char num[16];
            fgets(num,sizeof(num),fp);
            currentDelay = atoi(num);
            //printf("Setting delay to %u\n",currentDelay);
            break;
        }
        case '#':
        {
            int len = 0;
            focusIndex++;
            fgets(classname[focusIndex],MAX_NAME_LENGTH,fp);
            len = strlen(classname[focusIndex]);
            classname[focusIndex][len-1] = '\0';
            fgets(title[focusIndex],MAX_NAME_LENGTH,fp);
            len = strlen(title[focusIndex]);
            title[focusIndex][len-1] = '\0';
            //printf("Focus Shift to \"%s\" sub \"%s\"\n",classname[focusIndex],title[focusIndex]);
            break;
        }
        case '&':
            IF_DIRECTION(VK_TAB);
            break;
        case '$':
            IF_DIRECTION(VK_F5);
            break;
        case '*':
            IF_DIRECTION(VK_F4);
            break;
        case '@':
            IF_DIRECTION(VK_RETURN);
            break;
        case '~':
            IF_DIRECTION(VK_ESCAPE);
            break;
        case '^':
            IF_DIRECTION(VK_UP);
            break;
//      case '<':
//          IF_DIRECTION(VK_LEFT);
//          break;
//      case '>':
//          IF_DIRECTION(VK_RIGHT);
//          break;
        case '|':
            IF_DIRECTION(VK_DOWN);
            break;
        case '+': // key up
            if (last_state == KEY_STATE_KEY)
            {
                keys[key_index].direction = KEY_UP;
                last_state = KEY_STATE_DIRECTION;
            }
            break;
        case '_': // key down
            if (last_state == KEY_STATE_KEY)
            {
                keys[key_index].direction = KEY_DW;
                last_state = KEY_STATE_DIRECTION;
            }
            break;
        case '(': // shift + down
            MODIFIER(VK_SHIFT,KEY_DW);
            break;
        case '{': // ctrl + down
            MODIFIER(VK_LCONTROL,KEY_DW);
            break;
        case '<': // alt + down
            MODIFIER(VK_LMENU,KEY_DW);
            break;
        case ')': // shift + up
            MODIFIER(VK_SHIFT,KEY_UP);
            break;
        case '}': // ctrl + up
            MODIFIER(VK_LCONTROL,KEY_UP);
            break;
        case '>': // alt + up
            MODIFIER(VK_LMENU,KEY_UP);
            break;
        default:
            // we take letter/num combos and ,.;'/[]\-=`:
            if (isalpha(c) || isdigit(c) || c == ',' || c == '.' || c == ';' ||
                c == '\'' || c == '\\' || c == '/' || c == '[' || c == ']' ||
                c == '-' || c == '=' || c == '`' || c == ' ' || c == ':')
            {
                IF_DIRECTION(toupper(c));
            }
            else
            {
                printf("Unusable key \"%c\" [0x%02x]\n",c);
            }
            break;
        }
    } while (key_index < MAX_NUM_KEY_STROKES);
    return err;
}

typedef struct
{
    /** The title of the window to search for */
    char *title;
    /** An output variable to hold the result */
    HWND handle;
} FindWindowByName_str;

/**
 * A callback for the EnumWindows API.
 * @param handle A handle to a window
 * @param lParam A casted "FindWindowByName_str" pointer.
 *               This contains the pointer to the name we are searching for
 *               and it is used to assign the found handle.
 * @return Returns 1
 */
BOOL CALLBACK EnumWindowsProcCatchName(HWND handle, LPARAM lParam)
{
    char classname[100];
    char title[100];
    GetClassName(handle,classname,sizeof(classname));
    GetWindowText(handle,title,sizeof(title));
    if (classname[0] != '\0')
    {
        // try to catch the name
        FindWindowByName_str *p_fwbn = (FindWindowByName_str *)lParam;
        int len = strlen(p_fwbn->title);
        if (memcmp(title,p_fwbn->title,len)==0)
        {
            p_fwbn->handle = handle;
        }
    }
    return 1;
}

/**
 * Searches all available window titles for the one that matches
 * the parameter and returns that handle.
 * @param title The title to search for.
 * @return Returns the handle to the window, if it found it, 0 otherwise.
 */
HWND findWindowByTitle(char *title)
{
    FindWindowByName_str fwbn = {NULL,0};
    fwbn.title = title;
    EnumWindows(EnumWindowsProcCatchName,(LPARAM)&fwbn);
    return fwbn.handle;
}

/**
 * A callback for the EnumWindows API. This lists all the childern of the
 * current window.
 * @param handle A handle to a window
 * @param lParam A parameter passed to me from my caller.
 * @return Returns 1
 */
BOOL CALLBACK EnumChildWindowsProc(HWND handle, LPARAM lParam)
{
    char classname[100];
    char title[100];
    GetClassName(handle,classname,sizeof(classname));
    GetWindowText(handle,title,sizeof(title));
    if (classname[0] != '\0')
        printf("\t[0x%08x] \"%s\":\"%s\"\n",handle,classname,title);
    return 1;
}

/**
 * A callback for the EnumWindows API. This lists all the classed windows in
 * the system and their childern.
 * @param handle A handle to a window
 * @param lParam A parameter passed to me from my caller.
 * @return Returns 1
 */
BOOL CALLBACK EnumWindowsProc(HWND handle, LPARAM lParam)
{
    char classname[100];
    char title[100];
    GetClassName(handle,classname,sizeof(classname));
    GetWindowText(handle,title,sizeof(title));
    if (classname[0] != '\0')
    {
        printf("[0x%08x] \"%s\":\"%s\"\n",handle,classname,title);
        EnumChildWindows(handle,EnumChildWindowsProc,lParam);
    }
    return 1;
}

/**
 * Executes the list of key strokes from the global variables.
 * @return Returns 0
 */
int executeKeyStrokes()
{
    BYTE virtualKey;
    HWND windowHandle;
    BOOL set;
    int i;

    for (i=0; i < key_index; i++)
    {
        printf("\"%s\":\"%s\"\n",classname[keys[i].focus],title[keys[i].focus]);
        windowHandle = FindWindow(classname[keys[i].focus],title[keys[i].focus]);
        if (windowHandle == 0)
        {
            windowHandle = findWindowByTitle(title[keys[i].focus]);
        }
        set = SetForegroundWindow(windowHandle);
        if (set == 0)
        {
            printf("Could not set window focus on [0x%08x]!\n",windowHandle);
            break;
        }
        virtualKey = MapVirtualKey(keys[i].scanCode,0);
        printf("Key focus: %u\tDir: %s\tsC: %u\tvk: %u\n",keys[i].focus,(keys[i].direction==KEY_UP?"UP":"DW"),keys[i].scanCode,virtualKey);
        keybd_event( keys[i].scanCode, 0x00, keys[i].direction, 0);
        Sleep(keys[i].delay);
    }
    return 0;
}

/**
 * Main Loop
* @param argc The number of arguments to the program.
 * @param argv The array of pointer to arguments.
 * @return Returns 0 on success, anything else is failure.
 */
int main(int argc, char* argv[])
{
    int err = 0;
    if (argc > 1)
    {
        if (strcmp("-f",argv[1]) == 0)
        {
            FILE *fp = fopen(argv[2],"r");
            if (fp != NULL)
            {
                printf("Opening File!\n");
                parseKeyStrokes(fp,keys);
                fclose(fp);
                //printf("key_index = %u\n",key_index);
                executeKeyStrokes();
            }
        }
        else if (strcmp("-w",argv[1]) == 0)
        {
            EnumWindows(EnumWindowsProc,0);
        }
        else
        {
            Usage(argc, argv);
            err = -1;
        }
    }
    else
    {
        Usage(argc, argv);
    }
    return err;
}
